﻿using System;
using System.Globalization;
using System.Net;
using System.Windows;
using System.Windows.Controls;
using System.Windows.Documents;
using System.Windows.Ink;
using System.Windows.Input;
using System.Windows.Media;
using System.Windows.Media.Animation;
using System.Windows.Shapes;

namespace Gmantis.Controls.Extended
{
    [TemplatePart(Name = "InputBlue", Type = typeof(NumericBox)), 
    TemplatePart(Name = "Preview", Type = typeof(CheckeredBorder)), 
    TemplatePart(Name = "InputRed", Type = typeof(NumericBox)), 
    TemplatePart(Name = "InputGreen", Type = typeof(NumericBox)), 
    TemplatePart(Name = "OkButton", Type = typeof(Button)), 
    TemplatePart(Name = "InputLuminance", Type = typeof(NumericBox)), 
    TemplatePart(Name = "InputHue", Type = typeof(NumericBox)), 
    TemplatePart(Name = "InputSaturation", Type = typeof(NumericBox)), 
    TemplatePart(Name = "InputAlpha", Type = typeof(NumericBox)), 
    TemplatePart(Name = "InputWeb", Type = typeof(HexColorBox)), 
    TemplatePart(Name = "Spectrum", Type = typeof(SpectrumColorPicker))]
    public class AdvancedColorPickerPart : Control
    {
        // Fields
        internal NumericBox _elementInputAlpha;
        internal NumericBox _elementInputBlue;
        internal NumericBox _elementInputGreen;
        internal NumericBox _elementInputHue;
        internal NumericBox _elementInputLuminance;
        internal NumericBox _elementInputRed;
        internal NumericBox _elementInputSaturation;
        internal HexColorBox _elementInputWeb;
        internal Button _elementOkButton;
        internal CheckeredBorder _elementPreview;
        internal SpectrumColorPicker _elementSpectrum;
        internal bool _isLoaded;
        private bool _throwSelectedColorChanged = true;
        private bool _updating;
        public static readonly DependencyProperty FocusBrushProperty = DependencyProperty.Register("FocusBrush", typeof(Brush), typeof(AdvancedColorPickerPart), null);
        internal const string InputAlphaElementName = "InputAlpha";
        public static readonly DependencyProperty InputBackgroundProperty = DependencyProperty.Register("InputBackground", typeof(Brush), typeof(AdvancedColorPickerPart), null);
        internal const string InputBlueElementName = "InputBlue";
        public static readonly DependencyProperty InputForegroundProperty = DependencyProperty.Register("InputForeground", typeof(Brush), typeof(AdvancedColorPickerPart), null);
        internal const string InputGreenElementName = "InputGreen";
        internal const string InputHueElementName = "InputHue";
        internal const string InputLuminanceElementName = "InputLuminance";
        internal const string InputRedElementName = "InputRed";
        internal const string InputSaturationElementName = "InputSaturation";
        internal const string InputWebElementName = "InputWeb";
        public static readonly DependencyProperty MouseOverBrushProperty = DependencyProperty.Register("MouseOverBrush", typeof(Brush), typeof(AdvancedColorPickerPart), null);
        internal const string OkButtonElementName = "OkButton";
        public static readonly DependencyProperty PressedBrushProperty = DependencyProperty.Register("PressedBrush", typeof(Brush), typeof(AdvancedColorPickerPart), null);
        internal const string PreviewElementName = "Preview";
        public static readonly DependencyProperty SelectedColorProperty = DependencyProperty.Register("SelectedColor", typeof(Color), typeof(AdvancedColorPickerPart), new PropertyMetadata(Colors.Black, new PropertyChangedCallback(AdvancedColorPickerPart.OnSelectedColorPropertyChanged)));
        public static readonly DependencyProperty ShowAlphaChannelProperty = DependencyProperty.Register("ShowAlphaChannel", typeof(bool), typeof(AdvancedColorPickerPart), new PropertyMetadata(true));
        internal const string SpectrumElementName = "Spectrum";

        // Events
        public event EventHandler ColorPicked;
        public event EventHandler<PropertyChangedEventArgs<Color>> SelectedColorChanged;

        // Methods
        public AdvancedColorPickerPart()
        {
            base.DefaultStyleKey = typeof(AdvancedColorPickerPart);
        }

        private void _elementOkButton_Click(object sender, RoutedEventArgs e)
        {
            this.SelectedColor = this.GetCurrentAdvancedColor();
            this.RaiseColorPicked();
        }

        private Color GetCurrentAdvancedColor()
        {
            SolidColorBrush brush = (SolidColorBrush)this._elementPreview.Background;
            if (brush == null)
                return Colors.Black;
            else
                return brush.Color;
        }

        private T GetTemplateChild<T>(string childName, bool required, ref string errors) where T : class
        {
            DependencyObject templateChild = base.GetTemplateChild(childName);
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "template part", childName, templateChild, required, ref errors);
            return (templateChild as T);
        }

        private static Storyboard GetTemplateChildResource(FrameworkElement root, string resourceName, bool required, ref string errors)
        {
            return GetTemplateChildResource<Storyboard>(root, resourceName, required, ref errors);
        }

        private static T GetTemplateChildResource<T>(FrameworkElement root, string resourceName, bool required, ref string errors) where T : class
        {
            object child = root.Resources[resourceName];
            ApplyTemplateHelper.VerifyTemplateChild(typeof(T), "resource", resourceName, child, required, ref errors);
            return (child as T);
        }

        private void HandleKeyboard(object sender, System.Windows.Input.KeyEventArgs e)
        {
            if (e.Key == Key.Enter)
            {
                this.SelectedColor = this.GetCurrentAdvancedColor();
                e.Handled = true;
                this.RaiseColorPicked();
            }
        }

        private void HslChanged(object sender, PropertyChangedEventArgs<double> e)
        {
            if (!this._updating)
            {
                Color color = HslColor.ToColor((float)(this._elementInputHue.Value / 360.0), (float)(this._elementInputSaturation.Value / 100.0), (float)(this._elementInputLuminance.Value / 100.0));
                this.UpdatePad(color, true, false, true, false);
            }
        }

        private void InitializeInputAlphaPart()
        {
            this._elementInputAlpha.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.RgbaChanged);
            this._elementInputAlpha.Minimum = 0.0;
            this._elementInputAlpha.Maximum = 255.0;
            this._elementInputAlpha.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputBluePart()
        {
            this._elementInputBlue.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.RgbaChanged);
            this._elementInputBlue.Minimum = 0.0;
            this._elementInputBlue.Maximum = 255.0;
            this._elementInputBlue.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputGreenPart()
        {
            this._elementInputGreen.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.RgbaChanged);
            this._elementInputGreen.Minimum = 0.0;
            this._elementInputGreen.Maximum = 255.0;
            this._elementInputGreen.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputHuePart()
        {
            this._elementInputHue.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.HslChanged);
            this._elementInputHue.Minimum = 0.0;
            this._elementInputHue.Maximum = 360.0;
            this._elementInputHue.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputLuminancePart()
        {
            this._elementInputLuminance.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.HslChanged);
            this._elementInputLuminance.Minimum = 0.0;
            this._elementInputLuminance.Maximum = 100.0;
            this._elementInputLuminance.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputRedPart()
        {
            this._elementInputRed.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.RgbaChanged);
            this._elementInputRed.Minimum = 0.0;
            this._elementInputRed.Maximum = 255.0;
            this._elementInputRed.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputSaturationPart()
        {
            this._elementInputSaturation.ValueChanged += new EventHandler<PropertyChangedEventArgs<double>>(this.HslChanged);
            this._elementInputSaturation.Minimum = 0.0;
            this._elementInputSaturation.Maximum = 100.0;
            this._elementInputSaturation.RangeValidationMode = RangeValidationMode.AlwaysTruncate;
        }

        private void InitializeInputWebPart()
        {
        }

        private void InitializeOkButtonPart()
        {
            this._elementOkButton.Click += new RoutedEventHandler(this._elementOkButton_Click);
        }

        private void InitializeSpectrumPart()
        {
            this._elementSpectrum.ColorChanged += new EventHandler<PropertyChangedEventArgs<Color>>(this.UpdatePadFromHSLVisual);
        }

        private void OnAfterApplyTemplate()
        {
            this.UpdatePad(this.SelectedColor, true, true, true, true);
            base.AddHandler(UIElement.KeyDownEvent, new KeyEventHandler(this.HandleKeyboard), true);
        }

        public override void OnApplyTemplate()
        {
            string errors = string.Empty;
            base.OnApplyTemplate();
            this._isLoaded = true;
            this._elementPreview = this.GetTemplateChild<CheckeredBorder>("Preview", true, ref errors);
            this._elementInputRed = this.GetTemplateChild<NumericBox>("InputRed", true, ref errors);
            if (this._elementInputRed != null)
            {
                this.InitializeInputRedPart();
            }
            this._elementInputGreen = this.GetTemplateChild<NumericBox>("InputGreen", true, ref errors);
            if (this._elementInputGreen != null)
            {
                this.InitializeInputGreenPart();
            }
            this._elementInputBlue = this.GetTemplateChild<NumericBox>("InputBlue", true, ref errors);
            if (this._elementInputBlue != null)
            {
                this.InitializeInputBluePart();
            }
            this._elementInputLuminance = this.GetTemplateChild<NumericBox>("InputLuminance", true, ref errors);
            if (this._elementInputLuminance != null)
            {
                this.InitializeInputLuminancePart();
            }
            this._elementInputHue = this.GetTemplateChild<NumericBox>("InputHue", true, ref errors);
            if (this._elementInputHue != null)
            {
                this.InitializeInputHuePart();
            }
            this._elementInputSaturation = this.GetTemplateChild<NumericBox>("InputSaturation", true, ref errors);
            if (this._elementInputSaturation != null)
            {
                this.InitializeInputSaturationPart();
            }
            this._elementInputAlpha = this.GetTemplateChild<NumericBox>("InputAlpha", true, ref errors);
            if (this._elementInputAlpha != null)
            {
                this.InitializeInputAlphaPart();
            }
            this._elementInputWeb = this.GetTemplateChild<HexColorBox>("InputWeb", true, ref errors);
            if (this._elementInputWeb != null)
            {
                this.InitializeInputWebPart();
            }
            this._elementSpectrum = this.GetTemplateChild<SpectrumColorPicker>("Spectrum", true, ref errors);
            if (this._elementSpectrum != null)
            {
                this.InitializeSpectrumPart();
            }
            this._elementOkButton = this.GetTemplateChild<Button>("OkButton", true, ref errors);
            if (this._elementOkButton != null)
            {
                this.InitializeOkButtonPart();
            }
            if (!string.IsNullOrEmpty(errors))
            {
                this._isLoaded = false;
                if ((base.Template != null) && !PlatformIndependent.IsInDesignMode(this))
                {
                    throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, "Template cannot be applied to AdvancedColorPickerPart.\nDetails: {0}", new object[] { errors }));
                }
            }
            else
            {
                this.OnAfterApplyTemplate();
            }
        }

        private void OnSelectedColorChanged(Color oldValue)
        {
            this.UpdatePad(this.SelectedColor, true, true, true, true);
        }

        private static void OnSelectedColorPropertyChanged(DependencyObject d, DependencyPropertyChangedEventArgs e)
        {
            AdvancedColorPickerPart sender = d as AdvancedColorPickerPart;
            Color oldValue = (Color)e.OldValue;
            sender.OnSelectedColorChanged(oldValue);
            if ((sender.SelectedColorChanged != null) && sender._throwSelectedColorChanged)
            {
                PropertyChangedEventArgs<Color> args = new PropertyChangedEventArgs<Color>
                {
                    OldValue = (Color)e.OldValue,
                    NewValue = (Color)e.NewValue
                };
                sender.SelectedColorChanged(sender, args);
            }
        }

        private void RaiseColorPicked()
        {
            if (this.ColorPicked != null)
            {
                this.ColorPicked(this, new EventArgs());
            }
        }

        private void RgbaChanged(object sender, PropertyChangedEventArgs<double> e)
        {
            if (!this._updating)
            {
                Color color = Color.FromArgb(Convert.ToByte(this._elementInputAlpha.Value), Convert.ToByte(this._elementInputRed.Value), Convert.ToByte(this._elementInputGreen.Value), Convert.ToByte(this._elementInputBlue.Value));
                this.UpdatePad(color, false, true, true, true);
            }
        }

        private void UpdatePad(Color color, bool updateRGB, bool updateHSLFields, bool updatePad, bool updateAlpha)
        {
            if (this._isLoaded && !this._updating)
            {
                this._updating = true;
                if (!updateAlpha)
                {
                    color.A = (byte)this._elementInputAlpha.Value;
                }
                if (updateRGB)
                {
                    this._elementInputRed.Value = Convert.ToDouble(color.R);
                    this._elementInputGreen.Value = Convert.ToDouble(color.G);
                    this._elementInputBlue.Value = Convert.ToDouble(color.B);
                }
                if (updateHSLFields)
                {
                    HslColor color2 = new HslColor(color);
                    this._elementInputHue.Value = color2.Hue * 360f;
                    this._elementInputSaturation.Value = color2.Saturation * 100f;
                    this._elementInputLuminance.Value = color2.Luminance * 100f;
                }
                if (updatePad)
                {
                    this._elementSpectrum.Color = color;
                }
                if (updateAlpha)
                {
                    this._elementInputAlpha.Value = color.A;
                }
                this._elementPreview.Background = new SolidColorBrush(color);
                this._updating = false;
            }
        }

        private void UpdatePadFromHSLVisual(object sender, EventArgs e)
        {
            this.UpdatePad(this._elementSpectrum.Color, true, true, false, true);
        }

        private void UpdatePadFromRGBAInputs()
        {
            Color color = Color.FromArgb(Convert.ToByte(this._elementInputAlpha.Value), Convert.ToByte(this._elementInputRed.Value), Convert.ToByte(this._elementInputGreen.Value), Convert.ToByte(this._elementInputBlue.Value));
            this.UpdatePad(color, false, true, true, false);
        }

        // Properties
        public Brush FocusBrush
        {
            get { return (Brush)GetValue(FocusBrushProperty); }
            set { SetValue(FocusBrushProperty, value); }
        }

        public Brush InputBackground
        {
            get { return (Brush)GetValue(InputBackgroundProperty); }
            set { SetValue(InputBackgroundProperty, value); }
        }

        public Brush InputForeground
        {
            get { return (Brush)GetValue(InputForegroundProperty); }
            set { SetValue(InputForegroundProperty, value); }
        }

        public Brush MouseOverBrush
        {
            get { return (Brush)GetValue(MouseOverBrushProperty); }
            set { SetValue(MouseOverBrushProperty, value); }
        }

        public Brush PressedBrush
        {
            get { return (Brush)GetValue(PressedBrushProperty); }
            set { SetValue(PressedBrushProperty, value); }
        }

        public Color SelectedColor
        {
            get { return (Color)GetValue(SelectedColorProperty); }
            set { SetValue(SelectedColorProperty, value); }
        }

        public bool ShowAlphaChannel
        {
            get { return (bool)GetValue(ShowAlphaChannelProperty); }
            set { SetValue(ShowAlphaChannelProperty, value); }
        }
    }
}
